package com.itphy.plus;

import com.itphy.pojo.UserInfo;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

@Component
@Intercepts(@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class MybatisSqlInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        int limitIndex=0;

        String groupActive = PageHelper.getGroups();

        Object[] args = invocation.getArgs();
        MappedStatement statement = (MappedStatement) args[0];
        Object parameterObject =  args[1];
        boolean isPageFun = isPageFun(statement.getId());
        BoundSql boundSql = statement.getBoundSql(parameterObject);
        String sql = boundSql.getSql();

        Page page =null;

        //排序
        List<OrderByParam> orderByParams = new ArrayList<>();
        Class<?> paramClass = parameterObject.getClass();
        for (Field f : paramClass.getDeclaredFields()) {
            // 判断这个字段是否有MyField注解
            if (f.isAnnotationPresent(OrderBy.class)) {
                OrderBy a = f.getAnnotation(OrderBy.class);
                if(Arrays.asList(a.groups()).contains(groupActive) || a.groups().length==0){
                    orderByParams.add(new OrderByParam()
                            .setName(f.getName())
                            .setDescription(a.description())
                            .setPriority(a.priority())
                            .setSort(a.sort())
                    );
                }
            }
        }
        if (orderByParams.size() > 0 && !StringUtils.isEmpty(sql)) {
            sql += OrderByParam.renderOrderBy(orderByParams);
        }

        //分页
        if(isPageFun){
            page = PageHelper.getPage();
            //统计
            String sqlCount = "SELECT COUNT(*) FROM ( " + sql + " ) pageT";
            args[0] = paramInjectionToMappedStatement(statement, sqlCount, boundSql,true);
            page.setTotal(((List<Integer>) invocation.proceed()).get(0));
            //分页
            if((limitIndex=sql.toUpperCase().lastIndexOf("LIMIT"))!=-1){
                sql=sql.substring(0, limitIndex)+" LIMIT "+(page.getPageNum()-1)*page.getPageSize()+","+page.getPageSize();
            }else {
                sql+=" LIMIT "+(page.getPageNum()-1)*page.getPageSize()+","+page.getPageSize();
            }
        }

        //执行
        args[0] = paramInjectionToMappedStatement(statement, sql, boundSql,false);

//        System.out.println(sql);

        if(isPageFun){
            return Arrays.asList(page.build((List)invocation.proceed()));
        }

        return invocation.proceed();
    }

    @Override
    public Object plugin(Object obj) {
        return Plugin.wrap(obj, this);
    }

    @Override
    public void setProperties(Properties arg0) {
    }

    //注入
    private MappedStatement paramInjectionToMappedStatement(MappedStatement statement, String sql, BoundSql boundSql,boolean countFlag) {
        BoundSql newBoundSql = new BoundSql(statement.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject());

        for (ParameterMapping mapping : boundSql.getParameterMappings()) {
            String prop = mapping.getProperty();
            if (boundSql.hasAdditionalParameter(prop)) {
                Object value = boundSql.getAdditionalParameter(prop);
                newBoundSql.setAdditionalParameter(prop, value);
            }
        }

        return copyFromMappedStatement(statement, new BoundSqlSqlSource(newBoundSql), countFlag);
    }

    //countFlag 是否查询总条数
    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource, boolean countFlag) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(ms.getKeyProperties()[0]);
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        if (countFlag) {
            //自定义查询总条数
            builder.resultMaps(Arrays.asList(new ResultMap.Builder(ms.getConfiguration(), ms.getId(), Integer.class, Arrays.asList()).build()));
        } else {
             builder.resultMaps(ms.getResultMaps());
        }
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

    public static class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }

    //判断当前是否为分页方法
    private static boolean isPageFun(String method) throws Exception {
        Class<?> aClass = Class.forName(method.substring(0, method.lastIndexOf(".")));
        List<Method> collect = Arrays.asList(aClass.getMethods()).stream().filter(t -> {
            return method.substring(method.lastIndexOf(".")+1).equals(t.getName());
        }).collect(Collectors.toList());
        if(collect.size()>1){
            throw new RuntimeException(aClass.getName()+" 接口方法不能重名！！");
        }
        return Page.class.equals(collect.get(0).getReturnType()) ;
    }
}
